package model;

import model.interfaces.selectable.SelectableCommand;
import model.interfaces.selectable.SelectableInstance;
import model.interfaces.selectable.InstanceTypeAndHealth;
import _bye_util.IntRecycler;
import java.util.EnumMap;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;

/**
 * 
 * @author Chris
 * @editor alex kagioglu
 * 
 */
abstract class Unit implements SelectableInstance, ModelInstance, Individuals, NotPartOfTheSystem
{
    
    /**
     * 
     * defaults: state to powered up health to 100 max health to 100 empty
     * commands queue
     * 
     * Melee: armor 50 attack power 10 defense power 10 speed 3 ticks per tile 5
     * green upkeep
     * 
     * Ranged: armor 10 attack power 20 defense power 5 speed 2 ticks per tile 5
     * green upkeep
     * 
     * Colonist: armor 2 attack power 2 defense power 2 speed 1 ticks per tile 5
     * green upkeep
     * 
     * Explorer: armor 20 attack 5 defense 5 speed 1 upkeep 4
     * 
     * @param i
     *            is the type of unit to Construct
     */
    
    // attributes common to all units
    private State state;
    private int health;
    private int max_health;
    private Command command;
    private IntRecycler ir;
    private ArrayList< SelectableCommand > availCommands;
    
    // possible commands for all units
    private static SelectableCommand leaveArmy = new LeaveArmy();
    private static SelectableCommand powerUp = new PowerUp();
    private static SelectableCommand powerDown = new PowerDown();
    private static SelectableCommand goToRallyPoint = new GoToRallyPoint();
    private static SelectableCommand decommission = new Decommission();
    
    // attributes which have specific values for each type of Unit.
    private InstanceType type;
    private int armor;
    private int attack_power;
    private int defense_power;
    private int speed; // in steps per tile
    private String name;
    private Map< ResourceType, Integer > upkeep;
    
    // Each instantiation of a specific type of unit had a unique id that is
    // recyclable.
    private int id;
    
    // combat status variables
    private boolean recruited;
    
    public Unit( IntRecycler i, InstanceType type, int armor, int attack_power, int defense_power,
                 int speed, String name, int upkeepRed, int upkeepGreen, int upkeepBlue ) {
        // common to all Units.
        this.state = new State();
        this.health = 50; // starts with half health.
        this.max_health = 100;
        this.command = new Wait();
        this.ir = i;
        this.id = ir.newInt();
        
        // map recieved attributes
        // to local variables.
        this.type = type;
        this.armor = armor;
        this.attack_power = attack_power;
        this.defense_power = defense_power;
        this.speed = speed;
        this.name = name; // name is the name of the unit with a number
                          // concatenated.
        this.upkeep = new EnumMap< ResourceType, Integer >( ResourceType.class );
        this.upkeep.put( ResourceType.RED, upkeepRed );
        this.upkeep.put( ResourceType.GREEN, upkeepGreen );
        this.upkeep.put( ResourceType.BLUE, upkeepBlue );
        
        // initialize available commands
        availCommands = new ArrayList< SelectableCommand >();
        availCommands.add( goToRallyPoint );
        availCommands.add( powerDown );
        availCommands.add( decommission );
    }
    
    /**
     * @deprecated
     */
    protected State getState()
    {
        throw new UnsupportedOperationException();
    }
    
    // public methods
    /**
     * does damage to armor first, then to health.
     * 
     * @param damage
     *            is the amount of damage to deal to Unit.
     */
    public void takeDamage( int damage )
    {
        int armordamage = Math.min( this.armor, damage );
        this.armor = this.armor - armordamage;
        damage = damage - armordamage;
        
        this.health = Math.max( 0, this.health - damage );
    }
    
    public boolean isDead()
    {
        return health <= 0;
    }
    
    /**
     * hp goes from 0 to max_health
     * 
     * @param happy
     *            is amount of hp to give.
     */
    public void getHealed( int happy )
    {
        this.health = Math.min( this.max_health, this.health + happy );
    }
    
    /**
     * takes "speed" ticks for the unit to move the full horizontal or vertical
     * length of a tile.
     * 
     * @return speed in clock ticks per tile.
     */
    public int getStepsPerTile()
    {
        return speed;
    }
    
    // ***Interface Methods ***
    // SelectableInstance Interface methods
    /**
     * the head of the queue is in array
     * 
     * @return an array representation of the command queue.
     * @broken i'll fix it later. alex kagioglu
     */
    public SelectableCommand[] getCommands()
    {
        SelectableCommand[] zeCommands = new SelectableCommand[ availCommands.size() ];
        zeCommands = availCommands.toArray( zeCommands );
        return zeCommands;
    }
    
    public void throwItOnTheGround()
    {
        new ThrowItOnTheGround().cuzMyDadIsNotA( this );
    }
    
    /**
     * adds a command to this units command queue.
     */
    public void addCommandToQueue( SelectableCommand command )
    {
        this.command = command.getCommand();
    }
    
    public String getName()
    {
        return name;
    }
    
    /**
     * @return InstanceTypeAndHealth array of length 1 holding the type and
     *         health of this Unit.
     */
    public InstanceTypeAndHealth[] getStatus()
    {
        InstanceTypeAndHealth[] zeStatus = new InstanceTypeAndHealth[ 1 ];
        zeStatus[ 0 ] = new InstanceTypeAndHealthClass( type, health );
        return zeStatus;
    }
    
    /**
     * clears the entire command queue.
     */
    public void cancelPendingOrders()
    {
        command = new Wait();
    }
    
    // ModelInstance interface methods
    /**
     * removes the front command in the queue.
     */
    public void popCommand()
    {
        command = new Wait();
    }
    
    /**
     * @deprecated same function as cancelPending Orders.
     */
    public void clearQueue()
    {
        throw new UnsupportedOperationException();
    }
    
    public void powerUp()
    {
        state.incrementPower();
        
        if ( isPowered() )
        {
            availCommands.clear();
            availCommands.add( goToRallyPoint );
            availCommands.add( powerDown );
            availCommands.add( decommission );
            popCommand();
            
            if ( isRecruited() ) availCommands.add( leaveArmy );
        }
    }
    
    public void powerDown()
    {
        state.powerDown();
        availCommands.clear();
        availCommands.add( powerUp );
        availCommands.add( decommission );
        popCommand();
    }
    
    public boolean isPowered()
    {
        return state.isPowered();
    }
    
    /**
     * sets unit attributes to ineffective values. does not delete the unit.
     */
    public void decommission()
    {
        ir.recycleInt( id );
        health = 0;
    }
    
    /**
     * @return a map of which resourceType and how much of it is required to
     *         upkeep the unit on each tick. based on the state of the unit.
     *         (in/out of army, powered up/down)
     */
    public Map< ResourceType, Integer > getUpkeep()
    {
        // Units that are not part of an army are in standby mode
        // and consume resources at 75% of their normal rate.
        Map< ResourceType, Integer > temp;
        if ( this.state.isPowered() )
        {
            if ( this.recruited )
            {
                temp = this.upkeep;
            }
            else
            {
                temp = new EnumMap< ResourceType, Integer >( ResourceType.class );
                for ( ResourceType r : this.upkeep.keySet() )
                {
                    temp.put( r, (this.upkeep.get( r ) * 3) / 4 );
                }
            }
        }
        else
        {
            temp = new EnumMap< ResourceType, Integer >( ResourceType.class );
            for ( ResourceType r : this.upkeep.keySet() )
            {
                temp.put( r, this.upkeep.get( r ) / 4 );
            }
        }
        return temp;
    }
    
    /**
     * @return the front of the queue.
     */
    public Command onTick( Player player )
    {
        return command;
    }
    
    // Individuals Interface methods
    /**
     * @deprecated
     */
    public int getHealth()
    {
        throw new UnsupportedOperationException();
    }
    
    /**
     * @deprecated
     */
    public int getMaxHealth()
    {
        throw new UnsupportedOperationException();
    }
    
    /**
     * @deprecated
     */
    public int getArmor()
    {
        throw new UnsupportedOperationException();
    }
    
    public int getAttackPower()
    {
        return attack_power;
    }
    
    /**
	 * 
	 */
    public int getDefensePower()
    {
        throw new UnsupportedOperationException();
    }
    
    public InstanceType getInstanceType()
    {
        return type;
    }
    
    // Designer added methods
    // We added this because controller displays different options for this unit
    // based on if it's in an army or not.
    /**
     * @return recruited. True if the unit is in any army.
     */
    public boolean isRecruited()
    {
        return recruited;
    }
    
    /**
     * sets recruited to true.
     */
    public void recruit()
    {
        recruited = true;
        availCommands.add( leaveArmy );
    }
    
    /**
     * sets recruited to false.
     */
    public void leave_army()
    {
        recruited = false;
        availCommands.remove( leaveArmy );
    }
    
    /**
     * needed for controller / view to get the id of the unit
     */
    public int getID()
    {
        return id;
    }
    
    /**
     * @author - ryan
     */
    public void heal( int amount )
    {
        this.health = (health + amount) % max_health;
    }
    
    public boolean isFullyHealth()
    {
        return health >= max_health;
    }
    
    /**
     * Deviation:
     * 
     * If a unit being in a rally point allows the rally point access to
     * additional commands, the rally point will be able to get those commands
     * via this method.
     * 
     * @return the list of commands the unit adds to the rally point.
     */
    public abstract List< SelectableCommand > getAdditionalCommands();
}
